package com.doctcloud.common.webservice.utils;

import com.doctcloud.common.webservice.config.WebserviceProperties;
import com.doctcloud.common.webservice.exception.WebserviceInvokeException;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebService客户端调用工具类（静态方法+Spring注入双支持，适配doctCloud异常规范）
 * 功能：创建客户端代理、设置超时、简化方法调用，统一异常处理
 *
 * @author ruoyi
 */
@Component
public class WebserviceClientUtils {

    // 静态配置对象（通过Spring注入，供静态方法使用）
    private static WebserviceProperties webserviceProperties;
    // WebserviceClientUtils 中新增
    private static final Map<String, Object> PROXY_CACHE = new ConcurrentHashMap<>(); // 线程安全缓存
    /**
     * Spring注入Webservice配置（非静态方法注入，赋值给静态变量）
     * 注：避免使用@Autowired直接注入静态变量，符合Spring依赖注入规范
     */
    @Autowired
    public void setWebserviceProperties(WebserviceProperties properties) {
        WebserviceClientUtils.webserviceProperties = properties;
    }

    /**
     * 创建WebService客户端代理（通用核心方法）
     *
     * @param wsdlUrl    服务端WSDL地址（示例：http://localhost:8080/ws/hello?wsdl）
     * @param serviceCls 服务接口Class（客户端接口需与服务端全类名、方法签名完全一致）
     * @param <T>        接口泛型（需继承JAX-WS规范接口）
     * @return T 接口代理对象（直接调用接口方法即可发起SOAP请求）
     * @throws WebserviceInvokeException 当WSDL解析失败、代理创建异常时抛出
     */
    @SuppressWarnings("unchecked")
    public static <T> T createClientProxy(String wsdlUrl, Class<T> serviceCls) {
        // 校验入参：避免空指针（提前拦截无效请求）
        if (wsdlUrl == null || wsdlUrl.trim().isEmpty()) {
            throw new WebserviceInvokeException(
                    "ws.proxy.create.param.empty",
                    "创建WebService代理失败：WSDL地址不能为空",
                    new IllegalArgumentException("WSDL URL is null or empty")
            );
        }
        if (serviceCls == null) {
            throw new WebserviceInvokeException(
                    "ws.proxy.create.param.empty",
                    "创建WebService代理失败：服务接口Class不能为空",
                    new IllegalArgumentException("Service Class is null")
            );
        }

        try {
            // 1. 初始化CXF代理工厂（JAX-WS规范实现）
            JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
            // 设置WSDL地址（用于解析服务端接口结构、命名空间等元信息）
            factory.setWsdlLocation(wsdlUrl);
            // 设置服务接口Class（绑定代理对象与接口的关联）
            factory.setServiceClass(serviceCls);

            // 2. 启用日志拦截器（可选，由配置控制是否打印SOAP请求/响应）
            if (webserviceProperties != null && webserviceProperties.isEnableLogInterceptor()) {
                // 入站拦截器：打印服务端返回的SOAP响应日志
                factory.getInInterceptors().add(new LoggingInInterceptor());
                // 出站拦截器：打印客户端发送的SOAP请求日志（含参数、命名空间）
                factory.getOutInterceptors().add(new LoggingOutInterceptor());
            }

            // 3. 生成代理对象（底层通过动态代理创建接口实现类）
            T proxy = (T) factory.create();

            // 4. 设置客户端超时时间（避免调用阻塞，从配置读取超时值）
            setClientTimeout(proxy);

            return proxy;
        } catch (Exception e) {
            // 统一异常封装：将CXF原生异常（如WSDLException、RuntimeException）转为自定义异常
            throw new WebserviceInvokeException(
                    "ws.proxy.create.fail",  // 错误码（便于全局异常分类处理）
                    String.format("创建WebService客户端代理失败：WSDL地址=%s，接口Class=%s",
                            wsdlUrl, serviceCls.getName()),  // 异常详情（含关键参数，便于排查）
                    e  // 原始异常根源（保留异常链，不丢失底层报错信息）
            );
        }
    }

    /**
     * 直接调用WebService方法（简化版：自动创建代理+反射调用，无需手动处理代理对象）
     *
     * @param wsdlUrl    服务端WSDL地址
     * @param serviceCls 服务接口Class
     * @param methodName 要调用的接口方法名（需与服务端接口方法名完全一致，区分大小写）
     * @param params     方法参数（顺序、类型需与服务端接口方法参数完全匹配）
     * @param <T>        接口泛型
     * @return Object 服务端返回结果（需根据接口方法返回值类型强制转换）
     * @throws WebserviceInvokeException 当代理创建失败、方法反射调用异常时抛出
     */
    public static <T> Object invoke(String wsdlUrl, Class<T> serviceCls, String methodName, Object... params) {
        // 1. 先创建代理对象（复用createClientProxy方法，避免代码冗余）
        T proxy = createClientProxy(wsdlUrl, serviceCls);

        try {
            // 2. 反射获取接口方法（参数类型数组需与实际参数匹配）
            Class<?>[] paramTypes = getParamTypes(params);
            // 注：getMethod需传入"方法名+参数类型数组"，避免方法重载时匹配错误
            java.lang.reflect.Method method = serviceCls.getMethod(methodName, paramTypes);

            // 3. 反射调用方法（通过代理对象发起SOAP请求，底层自动处理XML序列化/反序列化）
            return method.invoke(proxy, params);
        } catch (NoSuchMethodException e) {
            // 特殊异常处理：方法不存在（大概率是方法名拼写错误、参数类型不匹配）
            throw new WebserviceInvokeException(
                    "ws.invoke.method.not.found",
                    String.format("调用WebService方法失败：接口=%s 不存在方法=%s（参数类型：%s）",
                            serviceCls.getName(), methodName, getParamTypesDesc(params)),
                    e
            );
        } catch (Exception e) {
            // 通用异常处理：反射调用异常（如参数类型不匹配、SOAP响应解析失败等）
            throw new WebserviceInvokeException(
                    "ws.invoke.fail",
                    String.format("调用WebService方法失败：WSDL=%s，接口=%s，方法=%s",
                            wsdlUrl, serviceCls.getName(), methodName),
                    e
            );
        }
    }

    /**
     * 设置客户端超时时间（避免调用阻塞，保护调用端资源）
     * 注：超时时间从配置文件读取，支持动态调整（无需修改代码）
     *
     * @param proxy 已创建的WebService接口代理对象
     * @param <T>   接口泛型
     * @throws WebserviceInvokeException 当代理对象非CXF客户端代理时抛出
     */
    private static <T> void setClientTimeout(T proxy) {
        // 校验配置对象：避免未注入配置时的空指针（兼容配置未加载场景）
        if (webserviceProperties == null) {
            throw new WebserviceInvokeException(
                    "ws.config.not.loaded",
                    "设置WebService超时失败：WebserviceProperties未加载（请检查Spring配置）",
                    new IllegalStateException("WebserviceProperties is null")
            );
        }

        try {
            // 1. 将接口代理对象转为CXF Client对象（获取底层通信控制能力）
            Client client = ClientProxy.getClient(proxy);
            // 2. 获取HTTP传输管道（控制SOAP请求的HTTP层参数：超时、编码、 headers等）
            HTTPConduit conduit = (HTTPConduit) client.getConduit();
            // 3. 初始化HTTP客户端策略（超时配置核心对象）
            HTTPClientPolicy policy = new HTTPClientPolicy();

            // 4. 设置超时时间（从配置读取，单位：毫秒）
            // 连接超时：建立HTTP连接的最大等待时间（避免无限等待）
            policy.setConnectionTimeout(webserviceProperties.getConnectTimeout());
            // 读取超时：获取SOAP响应的最大等待时间（避免长时间阻塞）
            policy.setReceiveTimeout(webserviceProperties.getReadTimeout());

            // 5. 将策略绑定到传输管道（生效超时配置）
            conduit.setClient(policy);
        } catch (ClassCastException e) {
            // 特殊异常：代理对象不是CXF Client（可能是其他JAX-WS实现的代理）
            throw new WebserviceInvokeException(
                    "ws.timeout.set.fail",
                    String.format("设置WebService超时失败：代理对象非CXF Client类型（代理类型：%s）",
                            proxy.getClass().getName()),
                    e
            );
        } catch (Exception e) {
            // 通用异常：超时配置过程异常（如管道获取失败）
            throw new WebserviceInvokeException(
                    "ws.timeout.set.fail",
                    "设置WebService客户端超时失败",
                    e
            );
        }
    }

    /**
     * 获取参数类型数组（反射调用方法时需传入，匹配方法签名）
     *
     * @param params 方法参数（可为null或空数组）
     * @return Class<?>[] 参数类型数组（长度与params一致，元素为对应参数的Class）
     */
    private static Class<?>[] getParamTypes(Object... params) {
        if (params == null || params.length == 0) {
            return new Class[0]; // 无参数时返回空数组（避免getMethod方法参数不匹配）
        }

        Class<?>[] types = new Class[params.length];
        for (int i = 0; i < params.length; i++) {
            Object param = params[i];
            // 处理null参数：null的Class无法直接获取，需提前约定参数类型（此处抛出异常提醒）
            if (param == null) {
                throw new WebserviceInvokeException(
                        "ws.invoke.param.null",
                        String.format("调用WebService方法失败：第%d个参数为null（无法确定参数类型，请避免传递null）",
                                i + 1),
                        new IllegalArgumentException("Param at index " + i + " is null")
                );
            }
            types[i] = param.getClass();
        }
        return types;
    }

    /**
     * 获取参数类型描述（用于异常信息展示，便于排查参数类型不匹配问题）
     * 示例：params为["test", 123]时，返回"String, Integer"
     */
    private static String getParamTypesDesc(Object... params) {
        if (params == null || params.length == 0) {
            return "无参数";
        }

        StringBuilder desc = new StringBuilder();
        for (int i = 0; i < params.length; i++) {
            if (i > 0) {
                desc.append(", ");
            }
            Object param = params[i];
            desc.append(param == null ? "null" : param.getClass().getSimpleName());
        }
        return desc.toString();
    }

    /**
     * 创建并缓存代理（复用代理，减少重复创建）
     * @param cacheKey 缓存键（如 "helloService:http://xxx/ws/hello?wsdl"）
     * @param wsdlUrl WSDL地址
     * @param serviceCls 接口Class
     * @param <T> 泛型
     * @return 缓存的代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createCachedClientProxy(String cacheKey, String wsdlUrl, Class<T> serviceCls) {
        return (T) PROXY_CACHE.computeIfAbsent(cacheKey, k -> createClientProxy(wsdlUrl, serviceCls));
    }

    /**
     * 清除代理缓存（如服务端地址变更时）
     * @param cacheKey 缓存键
     */
    public static void clearProxyCache(String cacheKey) {
        PROXY_CACHE.remove(cacheKey);
    }
}